// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/app_list/views/contents_view.h"

#include <algorithm>
#include <vector>

#include "base/logging.h"
#include "ui/app_list/app_list_constants.h"
#include "ui/app_list/app_list_switches.h"
#include "ui/app_list/app_list_view_delegate.h"
#include "ui/app_list/views/app_list_folder_view.h"
#include "ui/app_list/views/app_list_main_view.h"
#include "ui/app_list/views/apps_container_view.h"
#include "ui/app_list/views/apps_grid_view.h"
#include "ui/app_list/views/custom_launcher_page_view.h"
#include "ui/app_list/views/search_box_view.h"
#include "ui/app_list/views/search_result_list_view.h"
#include "ui/app_list/views/search_result_page_view.h"
#include "ui/app_list/views/search_result_tile_item_list_view.h"
#include "ui/app_list/views/start_page_view.h"
#include "ui/events/event.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/view_model.h"
#include "ui/views/widget/widget.h"

namespace app_list {

ContentsView::ContentsView(AppListMainView* app_list_main_view)
    : apps_container_view_(nullptr)
    , search_results_page_view_(nullptr)
    , start_page_view_(nullptr)
    , custom_page_view_(nullptr)
    , app_list_main_view_(app_list_main_view)
    , page_before_search_(0)
{
    pagination_model_.SetTransitionDurations(kPageTransitionDurationInMs,
        kOverscrollPageTransitionDurationMs);
    pagination_model_.AddObserver(this);
}

ContentsView::~ContentsView()
{
    pagination_model_.RemoveObserver(this);
}

void ContentsView::Init(AppListModel* model)
{
    DCHECK(model);

    AppListViewDelegate* view_delegate = app_list_main_view_->view_delegate();

    if (app_list::switches::IsExperimentalAppListEnabled()) {
        std::vector<views::View*> custom_page_views = view_delegate->CreateCustomPageWebViews(GetLocalBounds().size());
        // Only add the first custom page view as STATE_CUSTOM_LAUNCHER_PAGE. Ignore
        // any subsequent custom pages.
        if (!custom_page_views.empty()) {
            custom_page_view_ = new CustomLauncherPageView(custom_page_views[0]);

            AddLauncherPage(custom_page_view_,
                AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
        }

        // Start page.
        start_page_view_ = new StartPageView(app_list_main_view_, view_delegate);
        AddLauncherPage(start_page_view_, AppListModel::STATE_START);
    }

    // Search results UI.
    search_results_page_view_ = new SearchResultPageView();

    AppListModel::SearchResults* results = view_delegate->GetModel()->results();
    search_results_page_view_->AddSearchResultContainerView(
        results, new SearchResultListView(app_list_main_view_, view_delegate));

    if (app_list::switches::IsExperimentalAppListEnabled()) {
        search_results_page_view_->AddSearchResultContainerView(
            results, new SearchResultTileItemListView(GetSearchBoxView()->search_box(), view_delegate));
    }
    AddLauncherPage(search_results_page_view_,
        AppListModel::STATE_SEARCH_RESULTS);

    apps_container_view_ = new AppsContainerView(app_list_main_view_, model);

    AddLauncherPage(apps_container_view_, AppListModel::STATE_APPS);

    int initial_page_index = app_list::switches::IsExperimentalAppListEnabled()
        ? GetPageIndexForState(AppListModel::STATE_START)
        : GetPageIndexForState(AppListModel::STATE_APPS);
    DCHECK_GE(initial_page_index, 0);

    page_before_search_ = initial_page_index;
    // Must only call SetTotalPages once all the launcher pages have been added
    // (as it will trigger a SelectedPageChanged call).
    pagination_model_.SetTotalPages(app_list_pages_.size());

    // Page 0 is selected by SetTotalPages and needs to be 'hidden' when selecting
    // the initial page.
    app_list_pages_[GetActivePageIndex()]->OnWillBeHidden();

    pagination_model_.SelectPage(initial_page_index, false);

    ActivePageChanged();
}

void ContentsView::CancelDrag()
{
    if (apps_container_view_->apps_grid_view()->has_dragged_view())
        apps_container_view_->apps_grid_view()->EndDrag(true);
    if (apps_container_view_->app_list_folder_view()
            ->items_grid_view()
            ->has_dragged_view()) {
        apps_container_view_->app_list_folder_view()->items_grid_view()->EndDrag(
            true);
    }
}

void ContentsView::SetDragAndDropHostOfCurrentAppList(
    ApplicationDragAndDropHost* drag_and_drop_host)
{
    apps_container_view_->SetDragAndDropHostOfCurrentAppList(drag_and_drop_host);
}

void ContentsView::SetActiveState(AppListModel::State state)
{
    SetActiveState(state, true);
}

void ContentsView::SetActiveState(AppListModel::State state, bool animate)
{
    if (IsStateActive(state))
        return;

    SetActiveStateInternal(GetPageIndexForState(state), false, animate);
}

int ContentsView::GetActivePageIndex() const
{
    // The active page is changed at the beginning of an animation, not the end.
    return pagination_model_.SelectedTargetPage();
}

AppListModel::State ContentsView::GetActiveState() const
{
    return GetStateForPageIndex(GetActivePageIndex());
}

bool ContentsView::IsStateActive(AppListModel::State state) const
{
    int active_page_index = GetActivePageIndex();
    return active_page_index >= 0 && GetPageIndexForState(state) == active_page_index;
}

int ContentsView::GetPageIndexForState(AppListModel::State state) const
{
    // Find the index of the view corresponding to the given state.
    std::map<AppListModel::State, int>::const_iterator it = state_to_view_.find(state);
    if (it == state_to_view_.end())
        return -1;

    return it->second;
}

AppListModel::State ContentsView::GetStateForPageIndex(int index) const
{
    std::map<int, AppListModel::State>::const_iterator it = view_to_state_.find(index);
    if (it == view_to_state_.end())
        return AppListModel::INVALID_STATE;

    return it->second;
}

int ContentsView::NumLauncherPages() const
{
    return pagination_model_.total_pages();
}

void ContentsView::SetActiveStateInternal(int page_index,
    bool show_search_results,
    bool animate)
{
    if (!GetPageView(page_index)->visible())
        return;

    if (!show_search_results)
        page_before_search_ = page_index;

    app_list_pages_[GetActivePageIndex()]->OnWillBeHidden();

    // Start animating to the new page.
    pagination_model_.SelectPage(page_index, animate);
    ActivePageChanged();

    if (!animate)
        Layout();
}

void ContentsView::ActivePageChanged()
{
    AppListModel::State state = AppListModel::INVALID_STATE;

    std::map<int, AppListModel::State>::const_iterator it = view_to_state_.find(GetActivePageIndex());
    if (it != view_to_state_.end())
        state = it->second;

    app_list_pages_[GetActivePageIndex()]->OnWillBeShown();

    app_list_main_view_->model()->SetState(state);

    if (switches::IsExperimentalAppListEnabled()) {
        DCHECK(start_page_view_);

        // Set the visibility of the search box's back button.
        app_list_main_view_->search_box_view()->back_button()->SetVisible(
            state != AppListModel::STATE_START);
        app_list_main_view_->search_box_view()->Layout();
        bool folder_active = (state == AppListModel::STATE_APPS)
            ? apps_container_view_->IsInFolderView()
            : false;
        app_list_main_view_->search_box_view()->SetBackButtonLabel(folder_active);

        // Whenever the page changes, the custom launcher page is considered to have
        // been reset.
        app_list_main_view_->model()->ClearCustomLauncherPageSubpages();
    }

    app_list_main_view_->search_box_view()->ResetTabFocus(false);
}

void ContentsView::ShowSearchResults(bool show)
{
    int search_page = GetPageIndexForState(AppListModel::STATE_SEARCH_RESULTS);
    DCHECK_GE(search_page, 0);

    search_results_page_view_->ClearSelectedIndex();

    SetActiveStateInternal(show ? search_page : page_before_search_, show, true);
}

bool ContentsView::IsShowingSearchResults() const
{
    return IsStateActive(AppListModel::STATE_SEARCH_RESULTS);
}

void ContentsView::NotifyCustomLauncherPageAnimationChanged(double progress,
    int current_page,
    int target_page)
{
    int custom_launcher_page_index = GetPageIndexForState(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE);
    if (custom_launcher_page_index == target_page) {
        app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
            progress);
    } else if (custom_launcher_page_index == current_page) {
        app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
            1 - progress);
    }
}

void ContentsView::UpdatePageBounds()
{
    // The bounds calculations will potentially be mid-transition (depending on
    // the state of the PaginationModel).
    int current_page = std::max(0, pagination_model_.selected_page());
    int target_page = current_page;
    double progress = 1;
    if (pagination_model_.has_transition()) {
        const PaginationModel::Transition& transition = pagination_model_.transition();
        if (pagination_model_.is_valid_page(transition.target_page)) {
            target_page = transition.target_page;
            progress = transition.progress;
        }
    }

    NotifyCustomLauncherPageAnimationChanged(progress, current_page, target_page);

    AppListModel::State current_state = GetStateForPageIndex(current_page);
    AppListModel::State target_state = GetStateForPageIndex(target_page);

    // Update app list pages.
    for (AppListPage* page : app_list_pages_) {
        gfx::Rect to_rect = page->GetPageBoundsForState(target_state);
        gfx::Rect from_rect = page->GetPageBoundsForState(current_state);
        if (from_rect == to_rect)
            continue;

        // Animate linearly (the PaginationModel handles easing).
        gfx::Rect bounds(
            gfx::Tween::RectValueBetween(progress, from_rect, to_rect));

        page->SetBoundsRect(bounds);
        page->OnAnimationUpdated(progress, current_state, target_state);
    }

    // Update the search box.
    UpdateSearchBox(progress, current_state, target_state);
}

void ContentsView::UpdateSearchBox(double progress,
    AppListModel::State current_state,
    AppListModel::State target_state)
{
    AppListPage* from_page = GetPageView(GetPageIndexForState(current_state));
    AppListPage* to_page = GetPageView(GetPageIndexForState(target_state));

    SearchBoxView* search_box = GetSearchBoxView();

    gfx::Rect search_box_from(from_page->GetSearchBoxBounds());
    gfx::Rect search_box_to(to_page->GetSearchBoxBounds());
    gfx::Rect search_box_rect = gfx::Tween::RectValueBetween(progress, search_box_from, search_box_to);

    int original_z_height = from_page->GetSearchBoxZHeight();
    int target_z_height = to_page->GetSearchBoxZHeight();

    if (original_z_height != target_z_height) {
        gfx::ShadowValue original_shadow = GetShadowForZHeight(original_z_height);
        gfx::ShadowValue target_shadow = GetShadowForZHeight(target_z_height);

        gfx::Vector2d offset(gfx::Tween::LinearIntValueBetween(
                                 progress, original_shadow.x(), target_shadow.x()),
            gfx::Tween::LinearIntValueBetween(
                progress, original_shadow.y(), target_shadow.y()));
        search_box->SetShadow(gfx::ShadowValue(
            offset, gfx::Tween::LinearIntValueBetween(progress, original_shadow.blur(), target_shadow.blur()),
            gfx::Tween::ColorValueBetween(progress, original_shadow.color(),
                target_shadow.color())));
    }
    search_box->GetWidget()->SetBounds(
        search_box->GetViewBoundsForSearchBoxContentsBounds(
            ConvertRectToWidget(search_box_rect)));
}

PaginationModel* ContentsView::GetAppsPaginationModel()
{
    return apps_container_view_->apps_grid_view()->pagination_model();
}

void ContentsView::ShowFolderContent(AppListFolderItem* item)
{
    apps_container_view_->ShowActiveFolder(item);
}

void ContentsView::Prerender()
{
    apps_container_view_->apps_grid_view()->Prerender();
}

AppListPage* ContentsView::GetPageView(int index) const
{
    DCHECK_GT(static_cast<int>(app_list_pages_.size()), index);
    return app_list_pages_[index];
}

SearchBoxView* ContentsView::GetSearchBoxView() const
{
    return app_list_main_view_->search_box_view();
}

int ContentsView::AddLauncherPage(AppListPage* view)
{
    view->set_contents_view(this);
    AddChildView(view);
    app_list_pages_.push_back(view);
    return app_list_pages_.size() - 1;
}

int ContentsView::AddLauncherPage(AppListPage* view,
    AppListModel::State state)
{
    int page_index = AddLauncherPage(view);
    bool success = state_to_view_.insert(std::make_pair(state, page_index)).second;
    success = success && view_to_state_.insert(std::make_pair(page_index, state)).second;

    // There shouldn't be duplicates in either map.
    DCHECK(success);
    return page_index;
}

gfx::Rect ContentsView::GetDefaultSearchBoxBounds() const
{
    gfx::Rect search_box_bounds(0, 0, GetDefaultContentsSize().width(),
        GetSearchBoxView()->GetPreferredSize().height());
    if (switches::IsExperimentalAppListEnabled()) {
        search_box_bounds.set_y(kExperimentalSearchBoxPadding);
        search_box_bounds.Inset(kExperimentalSearchBoxPadding, 0);
    }
    return search_box_bounds;
}

gfx::Rect ContentsView::GetSearchBoxBoundsForState(
    AppListModel::State state) const
{
    AppListPage* page = GetPageView(GetPageIndexForState(state));
    return page->GetSearchBoxBounds();
}

gfx::Rect ContentsView::GetDefaultContentsBounds() const
{
    gfx::Rect bounds(gfx::Point(0, GetDefaultSearchBoxBounds().bottom()),
        GetDefaultContentsSize());
    return bounds;
}

bool ContentsView::Back()
{
    AppListModel::State state = view_to_state_[GetActivePageIndex()];
    switch (state) {
    case AppListModel::STATE_START:
        // Close the app list when Back() is called from the start page.
        return false;
    case AppListModel::STATE_CUSTOM_LAUNCHER_PAGE:
        if (app_list_main_view_->model()->PopCustomLauncherPageSubpage())
            app_list_main_view_->view_delegate()->CustomLauncherPagePopSubpage();
        else
            SetActiveState(AppListModel::STATE_START);
        break;
    case AppListModel::STATE_APPS:
        if (apps_container_view_->IsInFolderView())
            apps_container_view_->app_list_folder_view()->CloseFolderPage();
        else
            SetActiveState(AppListModel::STATE_START);
        break;
    case AppListModel::STATE_SEARCH_RESULTS:
        GetSearchBoxView()->ClearSearch();
        ShowSearchResults(false);
        break;
    case AppListModel::INVALID_STATE: // Falls through.
        NOTREACHED();
        break;
    }
    return true;
}

gfx::Size ContentsView::GetDefaultContentsSize() const
{
    return apps_container_view_->apps_grid_view()->GetPreferredSize();
}

gfx::Size ContentsView::GetPreferredSize() const
{
    gfx::Rect search_box_bounds = GetDefaultSearchBoxBounds();
    gfx::Rect default_contents_bounds = GetDefaultContentsBounds();
    gfx::Vector2d bottom_right = search_box_bounds.bottom_right().OffsetFromOrigin();
    bottom_right.SetToMax(
        default_contents_bounds.bottom_right().OffsetFromOrigin());
    return gfx::Size(bottom_right.x(), bottom_right.y());
}

void ContentsView::Layout()
{
    // Immediately finish all current animations.
    pagination_model_.FinishAnimation();

    double progress = IsStateActive(AppListModel::STATE_CUSTOM_LAUNCHER_PAGE) ? 1 : 0;

    // Notify the custom launcher page that the active page has changed.
    app_list_main_view_->view_delegate()->CustomLauncherPageAnimationChanged(
        progress);

    if (GetContentsBounds().IsEmpty())
        return;

    for (AppListPage* page : app_list_pages_) {
        page->SetBoundsRect(page->GetPageBoundsForState(GetActiveState()));
    }

    // The search box is contained in a widget so set the bounds of the widget
    // rather than the SearchBoxView.
    views::Widget* search_box_widget = GetSearchBoxView()->GetWidget();
    if (search_box_widget && search_box_widget != GetWidget()) {
        gfx::Rect search_box_bounds = GetSearchBoxBoundsForState(GetActiveState());
        search_box_widget->SetBounds(ConvertRectToWidget(
            GetSearchBoxView()->GetViewBoundsForSearchBoxContentsBounds(
                search_box_bounds)));
    }
}

bool ContentsView::OnKeyPressed(const ui::KeyEvent& event)
{
    bool handled = app_list_pages_[GetActivePageIndex()]->OnKeyPressed(event);

    if (!handled) {
        if (event.key_code() == ui::VKEY_TAB && event.IsShiftDown()) {
            GetSearchBoxView()->MoveTabFocus(true);
            handled = true;
        }
    }

    return handled;
}

void ContentsView::TotalPagesChanged()
{
}

void ContentsView::SelectedPageChanged(int old_selected, int new_selected)
{
    if (old_selected >= 0)
        app_list_pages_[old_selected]->OnHidden();

    if (new_selected >= 0)
        app_list_pages_[new_selected]->OnShown();
}

void ContentsView::TransitionStarted()
{
}

void ContentsView::TransitionChanged()
{
    UpdatePageBounds();
}

} // namespace app_list
